/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.mzk.lidovky.articles.articleImport;

import cz.mzk.lidovky.articles.domain.Article;
import cz.mzk.lidovky.articles.domain.ArticleComponent;
import cz.mzk.lidovky.articles.domain.Author;
import cz.mzk.lidovky.articles.domain.DayTime;
import cz.mzk.lidovky.articles.domain.Genre;
import cz.mzk.lidovky.articles.domain.Language;
import cz.mzk.lidovky.articles.domain.NumberInterval;
import cz.mzk.rehan.commons.Namespaces;
import cz.mzk.rehan.commons.StringUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.joda.time.LocalDateTime;

/**
 *
 * @author Martin Řehánek (rehan@mzk.cz)
 */
public class ArticleXml {

    private static final String LIBRATY_SIGLA = "BOA001";
    private static final int MAX_LABEL_LENGTH = 100;
    private Document document;
    private Article article;
    private Namespace foxml = Namespaces.foxml;
    private Namespace xsi = Namespaces.xsi;
    private Namespace oai_dc = Namespaces.oai_dc;
    private Namespace dc = Namespaces.dc;
    private Namespace mods = Namespaces.mods;
    private Namespace rdf = Namespaces.rdf;
    private Namespace kramerius = Namespaces.kramerius;
    private Namespace fedoraModel = Namespaces.fedora_model;
    private Namespace oai = Namespaces.oai;
    private final Periodical periodical;
    private final String pid;
    private final String uuid;
    private final Set<String> pagePids;
    private final String krameriusUrl;

    /**
     *
     * @param article
     * @param uuid generated uuid in format "79183069-c94c-11df-84b1-001b63bd97ba"
     * @param pagePids List of pids in format "pid:bdc405b0-e5f9-11dc-bfb2-000d606f5dc6"
     */
    public ArticleXml(Periodical periodical, Article article, String uuid, Set<String> pagePids, String krameriusUrl) {
        this.periodical = periodical;
        this.article = article;
        this.pagePids = pagePids;
        this.uuid = uuid;
        this.pid = "uuid:".concat(uuid);
        this.krameriusUrl = krameriusUrl;
        createDocument();
    }

    private void createDocument() {
        Element root = DocumentHelper.createElement(new QName("digitalObject", foxml));
        document = DocumentHelper.createDocument(root);
        document.getRootElement().add(foxml);
        document.getRootElement().add(xsi);
        document.getRootElement().addAttribute(new QName("schemaLocation", xsi), "info:fedora/fedora-system:def/foxml# http://www.fedora.info/definitions/1/0/foxml1-1.xsd");
        root.addAttribute("VERSION", "1.1");
        root.addAttribute("PID", pid);
        Element properties = root.addElement(new QName("objectProperties", foxml));
        addFoxmlProperties(properties);
        addDcDataStream(root);
        addModsDataStream(root);
        addRelsExtDataStream(root);
        //addPolicyDataStream(root);
        //probably will be created by Fedora
        //addFoxmlProperty(properties, "info:fedora/fedora-system:def/view#lastModifiedDate", getDateTime());
    }

    private void addFoxmlProperties(Element properties) {
        addFoxmlProperty(properties, "info:fedora/fedora-system:def/model#label", buildLabel());
        addFoxmlProperty(properties, "info:fedora/fedora-system:def/model#state", "A");
        //probably will be created by Fedora
        addFoxmlProperty(properties, "info:fedora/fedora-system:def/model#ownerId", "fedoraAdmin");
        //addFoxmlProperty(properties, "info:fedora/fedora-system:def/model#createdDate", getDateTime());
    }

    private String buildLabel() {
        String title = article.getTitle();
        if (title == null) {
            return " ";
        } else {
            return StringUtils.trimString(StringUtils.removeUndefinedUnicodeCharacters(title), MAX_LABEL_LENGTH, true);
        }
    }

    private void addFoxmlProperty(Element properties, String name, String value) {
        Element property = properties.addElement(new QName("property", foxml));
        property.addAttribute("NAME", name);
        property.addAttribute("VALUE", value);
    }

    private void addDcDataStream(Element element) {
        Element dataStream = addDataStream(element, "A", "X", "DC");
        Element dataStreamVersion = addDataStreamVersion(dataStream, "http://www.openarchives.org/OAI/2.0/oai_dc/", "text/xml", "Dublin Core Record for this object", "DC.0");
        Element xmlContent = dataStreamVersion.addElement(new QName("xmlContent", foxml));
        Element dcEl = xmlContent.addElement(new QName("dc", oai_dc));
        dcEl.add(dc);

        //title
        Element titleEl = dcEl.addElement(new QName("title", dc));
        String title = article.getTitle();
        if (title != null) {
            titleEl.addText(title);
        }

        //subtitle
        String subtitle = article.getSubTitle();
        if (subtitle != null) {
            Element subtitleEl = dcEl.addElement(new QName("title", dc));
            subtitleEl.addText(subtitle);
        }

        //creator
        Author author = article.getAuthor();
        if (author != null && !author.isUnknown()) {
            Element creator = dcEl.addElement(new QName("creator", dc));
            creator.addText(author.getUnresolvedName());
        }

        //identifier pid
        Element identifier = dcEl.addElement(new QName("identifier", dc));
        identifier.addText(pid);

        //type
        Element type = dcEl.addElement(new QName("type", dc));
        type.addText("model:article");

//        //genre
//        Element genreEl = dcEl.addElement(new QName("type", dc));
//        genreEl.addText("article");

        //language
        Element language = dcEl.addElement(new QName("language", dc));
        language.addText("cze");

        //form
        Element form = dcEl.addElement(new QName("format", dc));
        form.addText("electronic");

        //note
        String note = article.getNote();
        if (note != null) {
            Element description = dcEl.addElement(new QName("description", dc));
            description.addText(note);
        }

        //coverage
        Element coverage = dcEl.addElement(new QName("coverage", dc));
        coverage.addText(String.valueOf(article.totalPages()) + " s.");

        //rights
        Element rights = dcEl.addElement(new QName("rights", dc));
        rights.addText("policy:public");
    }

    private void addModsDataStream(Element element) {
        Element dataStream = addDataStream(element, "A", "X", "BIBLIO_MODS");
        Element dataStreamVersion = addDataStreamVersion(dataStream, null, "text/xml", "BIBLIO_MODS description of current object", "BIBLIO_MODS.0");
        Element xmlContent = dataStreamVersion.addElement(new QName("xmlContent", foxml));
        Element modsCollection = xmlContent.addElement(new QName("modsCollection", mods));
        Element modsEl = modsCollection.addElement(new QName("mods", mods));
        modsEl.addAttribute("version", "3.3");

        //identifier - sici
        //TODO: dodat sici identifier nebo nevytvaret element?
//        Element idSici = modsEl.addElement(new QName("identifier", mods));
//        idSici.addAttribute("type", "sici");
//        idSici.addText("TODO");

        //titleInfo
        Element titleInfo = modsEl.addElement(new QName("titleInfo", mods));
        titleInfo.addAttribute("ID", "MODS_ARTICLE");
        Element titleEl = titleInfo.addElement(new QName("title", mods));
        if (article.getTitle() != null) {
            titleEl.addText(article.getTitle());
        }
        if (article.getSubTitle() != null) {
            Element subTitle = titleInfo.addElement(new QName("subTitle", mods));
            subTitle.addText(article.getSubTitle());
        }

        //name
        Author author = article.getAuthor();
        if (author != null && !author.isUnknown()) {
            Element name = modsEl.addElement(new QName("name", mods));
            name.addAttribute("type", "personal");
            if (author.nameParsed()) {
                //surname
                if (author.hasSurname()) {
                    Element namePart = name.addElement(new QName("namePart", mods));
                    namePart.addAttribute("type", "family");
                    namePart.addText(author.getSurname());
                }

                //names
                if (author.hasFirstNames()) {
                    String[] firstNames = author.getFirstNames();
                    Element namePart;
                    for (int i = 0; i < firstNames.length; i++) {
                        namePart = name.addElement(new QName("namePart", mods));
                        namePart.addAttribute("type", "given");
                        namePart.addText(firstNames[i]);
                    }
                }

                //nick
                if (author.hasNick()) {
                    Element namePart = name.addElement(new QName("namePart", mods));
                    namePart.addAttribute("type", "termsOfAddress");
                    namePart.addText(author.getNick());
                }
            } else {
                Element namePart = name.addElement(new QName("namePart", mods));
                namePart.addText(author.getUnresolvedName());
            }

            //role
            Element role = name.addElement(new QName("role", mods));
            Element roleCode = role.addElement(new QName("roleTerm", mods));
            roleCode.addAttribute("type", "code");
            roleCode.addAttribute("authority", "marcrelator");
            roleCode.addText("cre");
            Element roleText = role.addElement(new QName("roleTerm", mods));
            roleText.addAttribute("type", "text");
            roleText.addText("Author");
        }
        //typeOfResource
        Element typeOfResource = modsEl.addElement(new QName("typeOfResource", mods));
        typeOfResource.addText("text");

        //genre
        Element genreEl = modsEl.addElement(new QName("genre", mods));
        genreEl.addAttribute("authority", "marcgt");
        genreEl.addText("article");

        //originInfo
        List<ArticleComponent> articleComponents = article.getArticleComponents();
        Element originInfo = modsEl.addElement(new QName("originInfo", mods));
        for (int i = 0; i < articleComponents.size(); i++) {
            Element dateEl;
            if (i == 0) {
                dateEl = originInfo.addElement(new QName("dateCreated", mods));
            } else {
                dateEl = originInfo.addElement(new QName("dateModified", mods));
            }
            dateEl.addAttribute("encoding", "iso8601");
            dateEl.addText(articleComponents.get(i).getIssue().getDate().toString());
        }

        //languageEl
        Language language = article.getLanguage();
        if (language
                == Language.unknown) {
            //davat cestinu, nebo nic???
            addModsLanguage(modsEl, Language.cz);
        } else {
            addModsLanguage(modsEl, language);
        }

        //physicalDescription
        Element physicalDescription = modsEl.addElement(new QName("physicalDescription", mods));
        Element form = physicalDescription.addElement(new QName("form", mods));
        form.addAttribute("authority", "marcform");
        form.addText("electronic");

        Element extent = physicalDescription.addElement(new QName("extent", mods));
        extent.addText(article.totalPages() + " s.");

        String info = article.getInfo();
        if (info != null) {
            Element noteEl = physicalDescription.addElement(new QName("note", mods));
            noteEl.addAttribute("type", "content");
            noteEl.addText(info);
        }
        String note = article.getNote();
        if (note != null) {
            Element noteEl = physicalDescription.addElement(new QName("note", mods));
            noteEl.addAttribute("type", "reproduction");
            noteEl.addText(note);
        }

        //subject
        Element subject = modsEl.addElement(new QName("subject", mods));
        if (article.getGenre().isList()) {
            for (Genre g : article.getGenre().getAllGenre()) {
                Element topic = subject.addElement(new QName("topic", mods));
                topic.addText(g.toString());
            }
        } else {
            Genre genre = article.getGenre();
            if (genre == null || genre == Genre.UNKNOWN) {
                genre = Genre.LITERATURE;
            }
            Element topic = subject.addElement(new QName("topic", mods));
            topic.addText(article.getGenre().toString());
        }

        //classification TODO: co s tim? zatim necham prazdne
//        Element classification = modsEl.addElement(new QName("classification", mods));
//
//        classification.addAttribute("authority", "ddc");
//        classification = modsEl.addElement(new QName("classification", mods));
//
//        classification.addAttribute("authority", "udc");

        //uuid
        Element identifier = modsEl.addElement(new QName("identifier", mods));
        identifier.addAttribute("type", "uuid");
        identifier.addText(uuid);

        //link
        Element location = modsEl.addElement(new QName("location", mods));
        Element url = location.addElement(new QName("url", mods));
        url.addText(buildKrameriusHandle(pid));

        //record info
        Element recordInfo = modsEl.addElement(new QName("recordInfo", mods));
        LocalDateTime now = new LocalDateTime();
        Element creationDate = recordInfo.addElement(new QName("recordCreationDate", mods));
        creationDate.addText(now.toString());
        Element changeDate = recordInfo.addElement(new QName("recordChangeDate", mods));
        changeDate.addText(now.toString());
        Element languageOfCataloging = recordInfo.addElement(new QName("languageOfCataloging", mods));
        Element languageTerm = languageOfCataloging.addElement(new QName("languageTerm", mods));
        languageTerm.addAttribute("authority", "iso639-2b");
        languageTerm.addAttribute("type", "code");
        languageTerm.addText("cze");

        Element recordOrigin = recordInfo.addElement(new QName("recordOrigin"));
        recordOrigin.addText("Vytvořeno konverzním nástrojem dostupným na"
                + " http://code.google.com/p/kramerius4-data-imports/ "
                + " z CSV dat ručně vytvořených v rámci projektu"
                + " Digializace moravských knihovních sbírek.");
        Element recordContentSource = recordInfo.addElement(new QName("recordContentSource", mods));
        recordContentSource.addText(LIBRATY_SIGLA);

        //relatedItem
        Element relatedItem = modsEl.addElement(new QName("relatedItem", mods));
        relatedItem.addAttribute("type", "host");
        Element perTitleInfo = relatedItem.addElement(new QName("titleInfo", mods));
        Element perTitle = perTitleInfo.addElement(new QName("title", mods));
        perTitle.addText(periodical.getName());

        //peridocial uuid
        Element perId = relatedItem.addElement(new QName("identifier", mods));
        perId.addAttribute("type", "uuid");
        perId.setText(periodical.getUuid());

        //peridoical issn
        Element perIssn = relatedItem.addElement(new QName("identifier", mods));
        perIssn.addAttribute("type", "issn");
        perIssn.addText(periodical.getIssn());

        //periodical - link
        Element perLocation = relatedItem.addElement(new QName("location", mods));
        Element perUrl = perLocation.addElement(new QName("url", mods));
        perUrl.addText(buildKrameriusHandle(periodical.getPid()));

        //parts
        for (ArticleComponent articleComponent : articleComponents) {
            Element part = relatedItem.addElement(new QName("part", mods));
            part.addAttribute("type", "article");
            //volume
            Element detail = part.addElement(new QName("detail", mods));
            detail.addAttribute("type", "volume");
            Element numberEl = detail.addElement(new QName("number", mods));
            numberEl.addText(String.valueOf(articleComponent.getVolume()));
            Element caption = detail.addElement(new QName("caption", mods));
            caption.addText("ročník");
            //issue
            detail = part.addElement(new QName("detail", mods));
            detail.addAttribute("type", "issue");
            numberEl = detail.addElement(new QName("number", mods));
            numberEl.addText(String.valueOf(articleComponent.getIssue().getNumber()));
            caption = detail.addElement(new QName("caption", mods));
            caption.addText("číslo");
            //edition
            DayTime dayTime = articleComponent.getIssue().getDayTime();
            if (dayTime != DayTime.unspecified) {
                detail = part.addElement(new QName("detail", mods));
                detail.addAttribute("type", "edition");
                titleEl = detail.addElement(new QName("title", mods));
                titleEl.addText(dayTime.toString());
                caption = detail.addElement(new QName("caption", mods));
                caption.addText("vydání");
            }
            //extent (page interval)
            extent = part.addElement(new QName("extent", mods));
            extent.addAttribute("unit", "page");
            NumberInterval pageInterval = articleComponent.getPageInterval();
            Element startEl = extent.addElement(new QName("start", mods));
            startEl.addText(String.valueOf(pageInterval.getStart()));
            Element endEl = extent.addElement(new QName("end", mods));
            endEl.addText(String.valueOf(pageInterval.getEnd()));
            Element total = extent.addElement(new QName("total", mods));
            total.addText(String.valueOf(pageInterval.size()));
        }
        Element accessCondition = modsEl.addElement(new QName("accessCondition", mods));
        accessCondition.addAttribute("type", "restrictionOnAccess");//TODO: jeste poresit
    }

    private String buildKrameriusHandle(String pid) {
        return krameriusUrl + "/handle/" + pid;
    }

    private void addModsLanguage(Element modsEl, Language language) {
        Element languageEl = modsEl.addElement(new QName("language", mods));
        String textValue = language.toIso639_2b();
        if (textValue != null) {
            Element languageTerm = languageEl.addElement(new QName("languageTerm", mods));
            languageTerm.addAttribute("authority", "iso639-2b");
            languageTerm.addAttribute("type", "code");
            languageTerm.addText(textValue);
        }

        textValue = language.toIso639_3();
        if (textValue != null) {
            Element languageTerm = languageEl.addElement(new QName("languageTerm", mods));
            languageTerm.addAttribute("authority", "iso639-3");
            languageTerm.addAttribute("type", "code");
            languageTerm.addText(textValue);
        }

        textValue = language.toRfc4646();
        if (textValue != null) {
            Element languageTerm = languageEl.addElement(new QName("languageTerm", mods));
            languageTerm.addAttribute("authority", "rfc4646");
            languageTerm.addAttribute("type", "code");
            languageTerm.addText(textValue);
        }
    }

    private void addRelsExtDataStream(Element element) {
        Element dataStream = addDataStream(element, "A", "E", "RELS-EXT");
        Element dataStreamVersion = addDataStreamVersion(dataStream, "info:fedora/fedora-system:FedoraRELSExt-1.0", "application/rdf+xml", "RDF Statements about this object", "RELS-EXT.0");
        Element xmlContent = dataStreamVersion.addElement(new QName("xmlContent", foxml));
        Element rdfEl = xmlContent.addElement(new QName("RDF", rdf));
        Element description = rdfEl.addElement(new QName("Description", rdf));
        description.addAttribute(new QName("about", rdf), "info:fedora/".concat(pid));
        Element hasModel = description.addElement(new QName("hasModel", fedoraModel));
        hasModel.addAttribute(new QName("resource", rdf), "info:fedora/model:article");
        Element itemId = description.addElement(new QName("itemID", oai));
        itemId.addText(pid);
        for (String pagePid : pagePids) {
            Element isOnPage = description.addElement(new QName("isOnPage", kramerius));
            isOnPage.addAttribute(new QName("resource", rdf), "info:fedora/" + pagePid);
        }
        Element policy = description.addElement(new QName("policy", kramerius));
        policy.addText("policy:public");
        //TODO:podle wiki by tady mel byt jeste nejaky element handle, v ukazkovem xml ale neni
    }

    private void addPolicyDataStream(Element element) {
        Element dataStream = addDataStream(element, "A", "E", "POLICY");
        Element dataStreamVersion = addDataStreamVersion(dataStream, null /*TODO: zjistit*/, "text/xml", "Policy datastream", "POLICY.0");
        Element contentLocation = dataStreamVersion.addElement(new QName("contentLocation", foxml));
        contentLocation.addAttribute("REF", "http://local.fedora.server/fedora/get/policy:private/POLICYDEF");
        contentLocation.addAttribute("TYPE", "URL");
    }

    private Element addDataStream(Element element, String state, String controlGroup, String id) {
        Element dataStream = element.addElement(new QName("datastream", foxml));
        dataStream.addAttribute("VERSIONABLE", "false");
        dataStream.addAttribute("STATE", state);
        dataStream.addAttribute("CONTROL_GROUP", controlGroup);
        dataStream.addAttribute("ID", id);
        return dataStream;
    }

    private Element addDataStreamVersion(Element element, String formatUri, String mimetype, String label, String id) {
        Element dataStreamVersion = element.addElement(new QName("datastreamVersion", foxml));
        if (formatUri != null) {
            dataStreamVersion.addAttribute("FORMAT_URI", formatUri);
        }
        dataStreamVersion.addAttribute("MIMETYPE", mimetype);
        //probably will be generated
        //dataStreamVersion.addAttribute("CREATED", getDateTime());
        if (label != null) {
            dataStreamVersion.addAttribute("LABEL", label);
        }
        dataStreamVersion.addAttribute("ID", id);
        return dataStreamVersion;
    }

    @Override
    public String toString() {
        return document.asXML().toString();
    }

    public void printDocument(File file) {
        try {
            FileOutputStream fos = new FileOutputStream(file);
            OutputFormat outformat = OutputFormat.createPrettyPrint();
            XMLWriter writer = new XMLWriter(fos, outformat);
            writer.write(document);
            writer.flush();
        } catch (FileNotFoundException e) {
            Logger.getLogger(ArticleXml.class.getName()).log(Level.SEVERE, null, e);
        } catch (UnsupportedEncodingException e) {
            Logger.getLogger(ArticleXml.class.getName()).log(Level.SEVERE, null, e);
        } catch (IOException e) {
            Logger.getLogger(ArticleXml.class.getName()).log(Level.SEVERE, null, e);
        }
    }

    /**
     *
     * @return Document containing created xml
     */
    public Document asDocument() {
        return document;
    }
}
